﻿using System;
using System.Collections.Generic;
using ICSharpCode.SharpZipLib.Zip;
using System.IO;
using ICSharpCode.SharpZipLib.Checksums;
namespace WindowServices.Common
{
    public class ZipHelper
    {

        public static string UnZipFile(string filename, string targetfilename, string password)
        {
            if (string.IsNullOrEmpty(filename))
            {
                throw new ArgumentNullException("filename");
            }

            if (string.IsNullOrEmpty(targetfilename))
            {
                throw new ArgumentNullException("targetfilename");
            }

            if (!File.Exists(filename))
            {
                throw new Exception("file not exit:" + filename);
            }

            using (FileStream stream = File.OpenRead(filename))
            {
                ZipInputStream zip = null;
                try
                {
                    zip = new ZipInputStream(stream);
                    if (!string.IsNullOrEmpty(password))
                    {
                        zip.Password = password;
                    }

                    ZipEntry entity = zip.GetNextEntry();
                    while (entity != null)
                    {
                        if (string.Compare(entity.Name, targetfilename, true) != 0)
                        {
                            continue;
                        }
                        using (MemoryStream stream1 = new MemoryStream())
                        {
                            WriteEntity(zip, entity, stream1);
                            using (StreamReader sr = new StreamReader(stream1))
                            {
                                return sr.ReadToEnd();
                            }
                        }                       
                    }
                }
                finally
                {
                    if (zip != null)
                    {
                        zip.Close();
                    }
                }
            }
            return null;
        }
        public static bool UnZip(string filename, string targetfolder, string password)
        {
            if (string.IsNullOrEmpty(filename))
            {
                throw new ArgumentNullException("filename");
            }

            if (string.IsNullOrEmpty(targetfolder))
            {
                throw new ArgumentNullException("targetfolder");
            }

            if (!File.Exists(filename))
            {
                throw new Exception("file not exit:" + filename);
            }

            using (FileStream stream = File.OpenRead(filename))
            {
                return UnZip(stream, targetfolder, password);
            }
            return true;
        }

        public static bool UnZip(Stream stream, string targetfoler, string password)
        {
            return UnZip(stream, new DirectoryInfo(targetfoler), password);
        }

        public static bool UnZip(Stream stream, DirectoryInfo targetfoler, string password)
        {
            if (stream == null)
            {
                throw new ArgumentNullException("stream");
            }

            if (!targetfoler.Exists)
            {
                targetfoler.Create();
            }

            ZipInputStream zip = null;
            try
            {
                zip = new ZipInputStream(stream);
                if (!string.IsNullOrEmpty(password))
                {
                    zip.Password = password;
                }

                ZipEntry entity = zip.GetNextEntry();
                while (entity != null)
                {
                    WriteEntity(zip, entity, targetfoler);
                    entity = zip.GetNextEntry();
                }
            }
            finally
            {
                if (zip != null)
                {
                    zip.Close();
                }
            }

            return true;
        }

        private static Stream UnZip(ZipInputStream zip)
        {
            byte[] buffer = new byte[BlockSize];
            MemoryStream file = new MemoryStream();
            int count = 0;
            do
            {
                count = zip.Read(buffer, 0, BlockSize);
                if (count > 0)
                {
                    file.Write(buffer, 0, count);
                }
            }
            while (count != BlockSize);
            return file;
        }

        private static int BlockSize = 2048;

        private static void WriteEntity(ZipInputStream zip, ZipEntry entity, DirectoryInfo targetfolder)
        {
            byte[] buffer = new byte[BlockSize];
            using (FileStream file = new FileStream(string.Format("{0}\\{1}", targetfolder, entity.Name), FileMode.OpenOrCreate, FileAccess.Write))
            {
                //int count = 0;
                //do
                //{
                //    count = zip.Read(buffer, 0, BlockSize);
                //    if (count > 0)
                //    {
                //        file.Write(buffer, 0, count);
                //    }
                //}
                //while (count == BlockSize);
                WriteEntity(zip, entity, file);
            }

        }

        private static void WriteEntity(ZipInputStream zip, ZipEntry entity, Stream stream)
        {
            byte[] buffer = new byte[BlockSize];
            int count = 0;
            do
            {
                count = zip.Read(buffer, 0, BlockSize);
                if (count > 0)
                {
                    stream.Write(buffer, 0, count);
                }
            }
            while (count == BlockSize);
        }

        public static bool Zip(string sourcefolder, string targetfile, string password, bool subfolder, Dictionary<string, Stream> dicmemoryfile)
        {
            if (string.IsNullOrEmpty(sourcefolder))
            {
                throw new ArgumentNullException("sourcefolder");
            }

            if (string.IsNullOrEmpty(targetfile))
            {
                throw new ArgumentNullException("targetfile");
            }

            if (!Directory.Exists(sourcefolder))
            {
                throw new Exception("folder is not exits:" + sourcefolder);
            }

            string tmp = Path.GetDirectoryName(targetfile);
            if (!Directory.Exists(tmp))
            {
                Directory.CreateDirectory(tmp);
            }

            ZipOutputStream zip = null;
            Crc32 crc = new Crc32();
            try
            {
                zip = new ZipOutputStream(new FileStream(targetfile, FileMode.OpenOrCreate, FileAccess.Write));
                if (!string.IsNullOrEmpty(password))
                {
                    zip.Password = password;
                }
                ZipFolder(ref zip, sourcefolder, subfolder, crc, sourcefolder);

                foreach (string filename in dicmemoryfile.Keys)
                {
                    WriteStream(ref zip, filename.Replace(sourcefolder, ""), dicmemoryfile[filename], crc);
                }

            }
            finally
            {
                if (zip != null)
                {
                    zip.Close();
                }
            }

            return true;
        }

        private static void ZipFolder(ref ZipOutputStream zip, string folder, bool subfolder, Crc32 crc, string cutString)
        {
            DirectoryInfo di = new DirectoryInfo(folder);
            foreach (FileInfo fi in di.GetFiles("*.*"))
            {
                using (FileStream fs = new FileStream(fi.FullName, FileMode.Open, FileAccess.Read))
                {
                    WriteStream(ref zip, fi.FullName.Replace(cutString, ""), fs, crc);
                }
            }

            if (subfolder)
            {
                foreach (DirectoryInfo di1 in di.GetDirectories())
                {
                    ZipFolder(ref zip, di1.FullName, true, crc, cutString);
                }
            }
        }

        private static void WriteStream(ref ZipOutputStream zip, string entityname, Stream stream, Crc32 crc)
        {
            byte[] buffer = new byte[BlockSize];
            int count = 0;
            ZipEntry entity = new ZipEntry(entityname);
            entity.DateTime = DateTime.Now;
            zip.PutNextEntry(entity);
            crc.Reset();
            do
            {
                count = stream.Read(buffer, 0, BlockSize);
                if (count > 0)
                {
                    crc.Update(buffer, 0, count);
                    zip.Write(buffer, 0, count);
                }
            }
            while (count == BlockSize);
            entity.Crc = crc.Value;
        }
    }
}
